#include <string.h>
#include <iostream>
#include <string>
#include "mysql_client.h"

using namespace std;

bool mysql_client::init(const std::string& host,
                        const std::string& user,
                        const std::string& password,
                        const std::string& database,
                        unsigned short port) {
  if (!(client_ = mysql_init(0))) {
    cout << "Couldn't initialize mysql struct" << endl;
    return false;
  }
  if (!mysql_real_connect(client_,
                          host.c_str(),
                          user.c_str(),
                          password.c_str(),
                          database.c_str(),
                          port,
                          nullptr,
                          0)) {
    cout << "Couldn't connect to engine! " << mysql_error(client_) << endl;
    return false;
  }
  return true;
}

void mysql_client::close() {
  mysql_close(client_);
}

bool mysql_client::select(std::string sql, std::vector<std::vector<std::string>>& results) {
  auto ret = mysql_real_query(client_, sql.c_str(), sql.size());
  if (0 != ret) {
    std::cout << "query failed, error:" << mysql_error(client_) << ", sql:" << sql << endl;
    return false;
  }
  auto* ms_res = mysql_store_result(client_);
  if (nullptr == ms_res) {
    std::cout << "store result failed, error:" << mysql_error(client_) << ", sql:" << sql << endl;
    return false;
  }

  auto field_num = mysql_num_fields(ms_res);
  auto field_info = mysql_fetch_field(ms_res);
  if (nullptr == field_info) {
    std::cout << "fetch field failed, error:" << mysql_error(client_) << ", sql:" << sql << endl;
    return false;
  }
  MYSQL_ROW row_data = nullptr;
  while ((row_data = mysql_fetch_row(ms_res)) != nullptr) {
    auto lengths = mysql_fetch_lengths(ms_res);
    std::vector<std::string> res;
    for (int i = 0; i < field_num; ++i) {
      if (row_data[i] == nullptr) {
        res.push_back(std::string());
      } else {
        auto len = lengths[i];
        std::string temp(row_data[i], len);
        res.push_back(temp);
      }
    }
    results.push_back(res);
  }
  mysql_free_result(ms_res);
  return true;
}

bool mysql_client::execute(std::vector<std::string> sqls) {
  if (sqls.empty()) {
    std::cout << "execute failed, sqls is empty." << std::endl;
    return false;
  }
  bool is_multi = sqls.size() > 1;
  if (!is_multi) {
    auto ret = mysql_real_query(client_, sqls.begin()->c_str(), sqls.begin()->size());
    if (0 != ret) {
      std::cout << "execute failed, error:" << mysql_error(client_) << ", sql:" << *sqls.begin()
                << endl;
      return false;
    }
    return true;
  }

  auto ret = mysql_real_query(client_, "BEGIN", strlen("BEGIN"));
  if (0 != ret) {
    std::cout << "execute failed, error:" << mysql_error(client_) << ", sql:"
              << "BEGIN" << endl;
    return false;
  }

  bool is_succ = true;
  for (auto&& sql : sqls) {
    auto ret = mysql_real_query(client_, sql.c_str(), sql.size());
    if (0 != ret) {
      std::cout << "execute failed, error:" << mysql_error(client_) << ", sql:" << sql << endl;
      is_succ = false;
      break;
    }
  }

  if (is_succ) {
    auto ret = mysql_real_query(client_, "COMMIT", strlen("COMMIT"));
    if (0 != ret) {
      std::cout << "execute failed, error:" << mysql_error(client_) << ", sql:"
                << "COMMIT" << endl;
      is_succ = false;
    }
  }

  if (!is_succ) {
    auto ret = mysql_real_query(client_, "ROLLBACK", strlen("ROLLBACK"));
    if (0 != ret) {
      std::cout << "execute failed, error:" << mysql_error(client_) << ", sql:"
                << "ROLLBACK" << endl;
    }
    return false;
  }

  return true;
}